import random
from django import forms
from django.conf import settings
from django.core.exceptions import ValidationError
from django.core.validators import RegexValidator
from django.db.models import Q
from django_redis import get_redis_connection
from web.forms.bootstrap import BootStrapForm
from utils import encrypt
from utils.tencent.sms import send_sms_single
from web import models

#############每个form类都要用到bookstrap的样式，不用每个都重写构造方法加上
# field.widget.attrs['class'] = 'form-control'
# field.widget.attrs['placeholder'] = '请输入{}'.format(field.label)


"""我们封装到一个类里去，让所有form类都继承它"""

class RegisterModelForm(BootStrapForm,forms.ModelForm):
    """用于注册页面显示以及数据校验"""

    # 这儿有认这儿，这儿没有找模型类
    mobile_phone = forms.CharField(label="手机号",validators=[RegexValidator(r'^(1[3|4|5|6|7|8|9])\d{9}$', '手机号格式错误'), ])
    password = forms.CharField(
        label='密码',
        min_length=8,
        max_length=64,
        error_messages={
            'min_length': "密码长度不能小于8个字符",
            'max_length': "密码长度不能大于64个字符"
        },
        widget=forms.PasswordInput()
    )
    confirm_password = forms.CharField(
        label='重复密码',
        min_length=8,
        max_length=64,
        error_messages={
            'min_length': "重复密码长度不能小于8个字符",
            'max_length': "重复密码长度不能大于64个字符"
        },
        widget=forms.PasswordInput()
    )
    code = forms.CharField(label='验证码',widget=forms.TextInput())

    # 意思就是判断用户名重复的钩子，这是说好的用clean_做前缀
    def clean_username(self):
        username = self.cleaned_data['username']
        if models.UserInfo.objects.filter(username=username).exists():
            raise ValidationError('用户名已存在')
        return username

    def clean_email(self):
        email = self.cleaned_data['email']
        if models.UserInfo.objects.filter(email=email).exists():
            raise ValidationError('邮箱已存在')
        return email

    def clean_password(self):
        password = self.cleaned_data['password']
        # 加密&返回(encrypt中我自己封装的)，下边重复密码肯定也要加密
        return encrypt.md5(password)

    def clean_confirm_password(self):
        password = self.cleaned_data['password']
        confirm_password = encrypt.md5(self.cleaned_data['confirm_password'])
        if password != confirm_password:
            raise ValidationError('两次密码不一致')
        return confirm_password

    def clean_mobile_phone(self):
        mobile_phone = self.cleaned_data['mobile_phone']
        if models.UserInfo.objects.filter(mobile_phone=mobile_phone).exists():
            raise ValidationError('手机号已被注册')
        return mobile_phone


    def clean_code(self):
        code = self.cleaned_data['code']
        # 不要用前面用的self.cleaned_data['mobile_phone']，主要是怕手机号输入了重复的，
        # 这里取不到，就会报错
        mobile_phone = self.cleaned_data.get('mobile_phone')
        conn = get_redis_connection()
        # 记住redis最底层的一定是字符串(是基于二进制安全的，不是我们常用的字符串)
        redis_code_bytes = conn.get(mobile_phone)
        if not redis_code_bytes:
            raise ValidationError('验证码失效或未发送，请重新失效')
        str_redis_code = redis_code_bytes.decode("utf8")
        # 给用户输入的验证码去一下空格
        if str_redis_code != code.replace(' ',''):
            raise ValidationError('验证码错误，请重新输入')
        return code

    class Meta:
        model = models.UserInfo
        fields = ["username","email","password","confirm_password","mobile_phone","code"]


class SendSmsForm(forms.Form):
    """只用于手机号和短信模板的校验，就不需要重写__init__方法给所有字段加class的css效果"""
    # 完成不为空还有格式正确的校验
    mobile_phone = forms.CharField(label="手机号",validators=[RegexValidator(r'^(1[3|4|5|6|7|8|9])\d{9}$', '手机号格式错误'), ])

    # 重写init方法(传进request)就是为了校验前端来的参数
    def __init__(self,request,*args,**kwargs):
        super().__init__(*args,**kwargs)
        self.request = request

    def clean_mobile_phone(self):
        """手机号和短信模板的校验钩子"""
        mobile_phone = self.cleaned_data['mobile_phone']
        # 判断模板是否有问题
        tpl = self.request.GET.get("tpl")
        tpl_id = settings.TENCENT_SMS_TEMPLATE.get(tpl)
        # 使用.get方法获取字典中的值，获取不到就为空不会报错
        if not tpl_id:
            # 这两种方式区别：raise是只要出现储物，就不往下继续判断剩下的字段，直接抛错误给前端
            # self.add_error('mobile_phone','短信模板错误信息')
            raise ValidationError("短信模板你都不知道，你是爬虫还是猪儿虫？")

        #####################本项目中中有登陆注册，如果还有重置密码模板还要elif
        if tpl=='login': #登录，手机号未注册抛异常
            if not models.UserInfo.objects.filter(mobile_phone=mobile_phone).exists():
                raise ValidationError('手机号还未注册')
        else: # 注册，手机号存在，抛异常
            if models.UserInfo.objects.filter(mobile_phone=mobile_phone).exists():
                raise ValidationError('手机号已经存在')

        # TODO:发短信&写入redis
        # 产生六位数验证码
        code = random.randint(100000, 999999)
        # 发送验证码短信
        res = send_sms_single(mobile_phone, tpl_id, [code, ])
        # 腾讯云会给我们返回json数据，包括状态码和错误信息,0表示发送成功
        print(res)
        if res['result'] != 0:
            raise ValidationError("短信发送失败".format(res['errmsg']))
        # 生成的正确code写入redis
        conn = get_redis_connection()
        conn.set(mobile_phone,code,ex=60)
        return mobile_phone


class LoginSmsForm(BootStrapForm,forms.Form):

    mobile_phone = forms.CharField(label="手机号",validators=[RegexValidator(r'^(1[3|4|5|6|7|8|9])\d{9}$', '手机号格式错误'), ])
    code = forms.CharField(label='验证码',widget=forms.TextInput())

    def clean_mobile_phone(self):
        """手机号和短信模板的校验钩子"""
        mobile_phone = self.cleaned_data['mobile_phone']
        if not models.UserInfo.objects.filter(mobile_phone=mobile_phone).exists():
            raise ValidationError('手机号还未注册')
        return mobile_phone

    def clean_code(self):
        """手机号和短信模板的校验钩子"""
        code = self.cleaned_data['code']
        mobile_phone = self.cleaned_data.get('mobile_phone')
        if not mobile_phone:
            return code
        conn = get_redis_connection()
        # 记住redis最底层的一定是字符串(是基于二进制安全的，不是我们常用的字符串)
        redis_code_bytes = conn.get(mobile_phone)
        if not redis_code_bytes:
            raise ValidationError('验证码失效或未发送，请重新失效')
        # 所以要做个解码操作
        str_redis_code = redis_code_bytes.decode("utf8")
        # 给用户输入的验证码去一下空格
        if str_redis_code != code.replace(' ', ''):
            raise ValidationError('验证码错误，请重新输入')
        return code


class LoginForm(BootStrapForm,forms.Form):
    username = forms.CharField(label="邮箱或手机号")
    password = forms.CharField(label="密码",widget=forms.PasswordInput())
    code = forms.CharField(label="图片验证码")

    # 有些事情比如用户注册入库，都会放在钩子中来做，
    # 视图中只有is_vaild,所以我们经常会通过重写__init__把request传进来
    def __init__(self,request,*args,**kwargs):
        super().__init__(*args,**kwargs)
        self.request = request

    # 该钩子用于加密传来的password，以及判断用户登录密码
    def clean_password(self):
        username = self.cleaned_data['username']
        password = encrypt.md5(self.cleaned_data['password'])
        # if models.UserInfo.objects.filter(username=username,password=password).exists():
        # 一行搞定手机密码登录######或######邮箱密码登录
        user_obj = models.UserInfo.objects.filter(Q(email=username) | Q(mobile_phone=username)).\
            filter(password=password).first()
        if not user_obj:
            raise ValidationError("用户名或密码错误！")
        # 登录成功，塞user_id进session
        self.request.session['user_id'] = user_obj.id
        self.request.session.set_expiry(14*24*60*60)
        return user_obj

    def clean_code(self):
        code = self.cleaned_data['code']
        session_code = self.request.session.get('image_code')
        if not session_code:
            raise ValidationError("验证码已经过期请点击重新获取！")
        if code.replace(' ','').upper() != session_code.upper():
            raise ValidationError("验证码输入错误！")
        return code